using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Globalization;

using MSHelpLib;
using MSHelpLib.CHM;
using MSHelpLib.BaseCode;

namespace MSHelpLib.CHM.Decoding
{
	/// <summary>
	/// Internal enumeration for specifying the type of the html-help file
	/// </summary>
	internal enum MSHelpLibFileType
	{
		/// <summary>
		/// CHM - compiled contents file
		/// </summary>
		/// <remarks>A file with this extension must always exist. If the file would be too long, some parts 
		/// can be splitted into the filestypes below.</remarks>
		CHM = 0,
		/// <summary>
		/// CHI - compiled system file
		/// </summary>
		CHI = 1,
		/// <summary>
		/// CHQ - compiled fulltext search file
		/// </summary>
		CHQ = 2,
		/// <summary>
		/// CHW - compiled index file
		/// </summary>
		CHW = 3

	}

	/// <summary>
	/// The class <c>CHMFile</c> implemts methods and properties for handling a single chmfile.
	/// </summary>
	public sealed class CHMFile : BaseHelpFile, IDisposable, IHelpFile
	{				
		/// <summary>
		/// Internal member storing the read information types
		/// </summary>
		private ArrayList _informationTypes = new ArrayList();
		/// <summary>
		/// Internal member storing the read categories
		/// </summary>
		private ArrayList _categories = new ArrayList();
		
		/// <summary>
		/// Internal member storing the full filename
		/// </summary>
		private string _chmFileName = "";
		/// <summary>
		/// Internal member storing the full filename of the chi-file (includes all system files)
		/// The file name is zero-length if there is no chi-file
		/// </summary>
		private string _chiFileName = "";
		/// <summary>
		/// Internal member storing the full filename of the chw-file (includes the help index)
		/// The file name is zero-length if there is no chw-file
		/// </summary>
		private string _chwFileName = "";
		/// <summary>
		/// Internal member storing the full filename of the chq-file (includes the fulltext contents)
		/// The file name is zero-length if there is no chq-file
		/// </summary>
		private string _chqFileName = "";
		/// <summary>
		/// Internal member storing the decoded information from the internal #SYSTEM file
		/// </summary>
		private CHMSystem _systemFile = null;
		/// <summary>
		/// Internal member storing the decoded information from the internal #IDXHDR file
		/// </summary>
		private CHMIdxhdr _idxhdrFile = null;
		/// <summary>
		/// Internal member storing the decoded information from the internal #STRINGS file
		/// </summary>
		private HelpStrings _stringsFile = null;
		/// <summary>
		/// Internal member storing the decoded information from the internal #URLSTR file
		/// </summary>
		private Urlstr _urlstrFile = null;
		/// <summary>
		/// Internal member storing the decoded information from the internal #URLTBL file
		/// </summary>
		private Urltable _urltblFile = null;
		/// <summary>
		/// Internal member storing the decoded information from the internal #TOPICS file
		/// </summary>
		private Topics _topicsFile = null;
		/// <summary>
		/// Internal member storing the decoded information from the internal #TOCIDX file
		/// </summary>
		private CHMTocidx _tocidxFile = null;
		/// <summary>
		/// Internal member storing the decoded information from the internal binary index file (KLinks).
		/// </summary>
		private CHMBtree _kLinks = null;
		/// <summary>
		/// Internal member storing the decoded information from the internal binary index file (ALinks).
		/// </summary>
		private CHMBtree _aLinks = null;
		/// <summary>
		/// Internal member storing the fulltext searcher for this file
		/// </summary>
		private FullTextEngine _ftSearcher = null;
		
		/// <summary>
		/// Internal memebr storing the chm file info
		/// </summary>
		private ChmFileInfo _chmFileInfo = null;
		
		internal string CHMName="";

		private CHMStream _currentWrapper;
		private CHMStream _baseStream=null;
        /// <summary>
        /// Gets the base stream.
        /// </summary>
        /// <value>The base stream.</value>
		public override IHelpStream BaseStream
		{
			get 
			{ 
				if (_baseStream==null)
				{
					_baseStream=new CHMStream();
					_baseStream.Open(ChmFilePath,this);
				}
				return _baseStream; 
			}
		}
		
		/// <summary>
		/// Creates a new instance of the class
		/// </summary>
		/// <param name="systemInstance">a reference to the hosting MSHelpLibSystem instance</param>
		/// <param name="chmFile">chm file to read</param>
		public CHMFile(IHelpSystem systemInstance,  string chmFile) : this(systemInstance,  chmFile, null, null)
		{
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="CHMFile"/> class.
        /// </summary>
        /// <param name="systemInstance">The system instance.</param>
        /// <param name="chmFile">The CHM file.</param>
        /// <param name="SH">The SH.</param>
        /// <param name="PH">The PH.</param>
		public CHMFile(IHelpSystem systemInstance,  string chmFile, StatusHandler SH, ProgressHandler PH) : this(systemInstance,  chmFile, (int)(LoadFlags.LoadDumpFile|LoadFlags.SaveDumpFile|LoadFlags.CreateSearchIndexOnLoad), SH,PH)
		{
		}

        /// <summary>
        /// Creates a new instance of the class
        /// </summary>
        /// <param name="systemInstance">The system instance.</param>
        /// <param name="chmFile">The CHM file.</param>
        /// <param name="flags">The flags.</param>
		internal CHMFile(IHelpSystem systemInstance, string chmFile, int flags)
			: this(systemInstance, chmFile, flags, null,null)
		{
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="CHMFile"/> class.
        /// </summary>
        /// <param name="systemInstance">The system instance.</param>
        /// <param name="chmFile">The CHM file.</param>
        /// <param name="_flags">The _flags.</param>
        /// <param name="SH">The SH.</param>
        /// <param name="PH">The PH.</param>
		public CHMFile(IHelpSystem systemInstance, string chmFile, int _flags, StatusHandler SH, ProgressHandler PH)
		{
			Open(systemInstance,chmFile,_flags ,SH,PH);
		}

		/// <summary>
		/// Read a IStorage file
		/// </summary>
		/// <param name="fname">filename</param>
		/// <param name="type">type of file</param>
		private void ReadFile(string fname, MSHelpLibFileType type)
		{			
			FileInfo fi=new FileInfo(fname);
			OnStatus("Reading "+fi.Name);

			CHMStream iw=null;
			iw=new CHMStream();			
			iw.Open(fname,this);
			_currentWrapper=iw;
			MemoryStream fileObject=null;
		
			if( (type != MSHelpLibFileType.CHQ) && (type != MSHelpLibFileType.CHW) )
			{								
				OnStatus("Extracting /SYSTEM file");
				fileObject = iw.Extract("/#SYSTEM");
				OnStatus("Processing");
				if ((fileObject != null) && (fileObject.Length>0))
				{
					_systemFile = new CHMSystem(fileObject.ToArray(), this);										
				}

				if (_systemFile!=null && (_systemFile.BinaryIndex || _systemFile.BinaryTOC || _systemFile.FullTextSearch))
				{
					OnStatus("Extracting /IDXHDR file");
					fileObject = iw.Extract("/#IDXHDR");
					OnStatus("Processing");
					if ((fileObject != null) && (fileObject.Length>0))
					{
						_idxhdrFile = new CHMIdxhdr(fileObject.ToArray(), this);										
					}
					
					OnStatus("Extracting /STRINGS file");
					fileObject = iw.Extract( "/#STRINGS");
					OnStatus("Processing");
					if ((fileObject != null) && (fileObject.Length>0))
					{						
						_stringsFile = new HelpStrings(fileObject.ToArray(), this);
					}

					OnStatus("Extracting /URLSTR file");
					fileObject = iw.Extract( "/#URLSTR");
					OnStatus("Processing");
					if ((fileObject != null) && (fileObject.Length>0))
					{
						_urlstrFile = new Urlstr(fileObject.ToArray(), this);
					}
				
					OnStatus("Extracting /URLTBL file");
					fileObject = iw.Extract( "/#URLTBL");
					OnStatus("Processing");
					if ((fileObject != null) && (fileObject.Length>0))
					{
						_urltblFile = new Urltable(fileObject.ToArray(), this);
					}				

					OnStatus("Extracting /TOPICS file");
					fileObject = iw.Extract( "/#TOPICS");
					OnStatus("Processing");
					if ((fileObject != null) && (fileObject.Length>0))
					{						
						_topicsFile = new Topics(fileObject.ToArray(), this);					
					}
				}
				
				if(!m_onlySystemData)
				{
					OnStatus("Extracting /TOCIDX file");
					fileObject = iw.Extract( "/#TOCIDX");
					OnStatus("Processing");
					if( (fileObject != null) && (fileObject.Length>0) && (_systemFile.BinaryTOC) )
					{							
						_tocidxFile = new CHMTocidx(fileObject.ToArray(), this);
						_toc = _tocidxFile.TOC;
					}					
				}

				if( (_systemFile != null) && (!m_onlySystemData) )
				{
					if(!_systemFile.BinaryTOC)
					{					
						IFileInfo HHCInfo=iw.FindFileByExt(".hhc");
						if (HHCInfo!=null)
						{
							OnStatus("Extracting Table Of Contents file");
							fileObject = iw.Extract(HHCInfo);		
							OnStatus("Processing");
							if ((fileObject != null) && (fileObject.Length>0))
							{
								ASCIIEncoding ascii=new ASCIIEncoding();
								OnStatus("Reading Table of Contents file");
								string hhcString =ascii.GetString(fileObject.ToArray(),0,(int)fileObject.Length);
								_toc = HHCParser.ParseHHC(hhcString, this);
							}

							if(HHCParser.HasMergeLinks)
								_mergeLinks = HHCParser.MergeItems;
						}
					} 
				}
			}

			if( type != MSHelpLibFileType.CHQ ) // no index information in CHQ files (only fulltext search)
			{
				if( (_systemFile != null) && (!m_onlySystemData) )
				{
					if( ! _systemFile.BinaryIndex )
					{
						OnStatus("Extracting Index file");
						fileObject = iw.Extract("/"+ _systemFile.IndexFile);
						OnStatus("Processing");

						if (fileObject == null)
						{
							IFileInfo HHKInfo=iw.FindFileByExt(".hhk");							
                            if (HHKInfo!=null)
							    fileObject = iw.Extract(HHKInfo);
						}

						if ((fileObject != null) && (fileObject.Length>0))
						{
							OnStatus("Reading Index File");
							string fileString = this.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length);								
							fileObject.Close();

							_indexKLinks = HHKParser.ParseHHK(fileString, this);												
							OnStatus("Processing...");
						}						
					} 
					else 
					{
						OnStatus("Extracting KIndex File");
						fileObject=iw.Extract(@"/$WWKeywordLinks/BTree");
						OnStatus("Processing");
						if ((fileObject != null) && (fileObject.Length>0))
						{
							OnStatus("Reading Keyword Links");
							_kLinks = new CHMBtree(fileObject.ToArray(), this);
							_indexKLinks = _kLinks.IndexList;
							OnStatus("Processing...");
						}

						OnStatus("Extracting AIndex file");
						fileObject =iw.Extract(@"/$WWAssociativeLinks/BTree");
						OnStatus("Processing");
						if ((fileObject != null) && (fileObject.Length>0))
						{						
							OnStatus("Reading Associative Links");
							_aLinks = new CHMBtree(fileObject.ToArray(), this);
							_indexALinks = _aLinks.IndexList;
							OnStatus("Sorting Links");
							_indexALinks.Sort(new SortIndexItems());
							OnStatus("Processing...");	
						}
						
						// Free up objects as they are not needed any more.
						// if (_idxhdrFile!=null)	_idxhdrFile.Dispose();	_idxhdrFile=null;
						// if (_stringsFile!=null)	_stringsFile.Dispose();	_stringsFile=null;
						// if (_urlstrFile!=null)	_urlstrFile.Dispose();	_urlstrFile=null;
						// if (_urltblFile!=null)	_urltblFile.Dispose();	_urltblFile=null;
						// if (_topicsFile!=null)	_topicsFile.Dispose();	_topicsFile=null;
					}
				}
			}

//			if( (type != MSHelpLibFileType.CHI) && (type != MSHelpLibFileType.CHW) )
//			{
//				if( (_systemFile != null) && (!m_onlySystemData) )
//				{
//					if( _systemFile.FullTextSearch)
//					{
//						OnStatus("Extracting FIftMain file");
//						fileObject = iw.Extract("/$FIftiMain");
//						OnStatus("Processing");
//						if(( fileObject != null) && (fileObject .Length>0))
//						{
//							OnStatus("Reading Search Data");
//							_ftSearcher = new FullTextEngine(fileObject .ToArray(), this);							
//							OnStatus("Processing...");
//						}
//					}
//				}
//			}
			_currentWrapper=null;
			OnStatus("Processing...");
		}

		/// <summary>
		/// Searches an TOC entry using the local
		/// </summary>
		/// <param name="local">local to search</param>
		/// <returns>Returns the TOC item</returns>
		internal TOCItem GetTOCItemByLocal(string local)
		{
			return GetTOCItemByLocal(this.TOC, local);
		}

		/// <summary>
		/// Recursively searches an TOC entry using its local
		/// </summary>
		/// <param name="arrTOC">toc level list</param>
		/// <param name="local">local to search</param>
		/// <returns>Returns the TOC item</returns>
		private TOCItem GetTOCItemByLocal(ArrayList arrTOC, string local)
		{
			TOCItem ret = null;
			foreach(TOCItem curItem in arrTOC)
			{
				string scL = curItem.Local.ToLower();
				string sL = local.ToLower();

				while(scL[0]=='/') // delete prefixing '/'
					scL = scL.Substring(1);

				while(sL[0]=='/') // delete prefixing '/'
					sL = sL.Substring(1);

				if(scL == sL)
					return curItem;

				if(curItem.Children.Count > 0)
				{
					ret =  GetTOCItemByLocal(curItem.Children, local);
					if(ret != null)
						return ret;
				}
			}

			return ret;
		}

		/// <summary>
		/// Removes a TOCItem from the toc
		/// </summary>
		/// <param name="rem">item to remove</param>
		/// <returns>Returns true if removed</returns>
		internal bool RemoveTOCItem(TOCItem rem)
		{
			if(rem == null)
				return false;

			return RemoveTOCItem(this.TOC, rem);
		}

		/// <summary>
		/// Recursively searches a TOCItem and removes it if found
		/// </summary>
		/// <param name="arrTOC">toc level list</param>
		/// <param name="rem">item to remove</param>
		/// <returns>Returns true if removed</returns>
		private bool RemoveTOCItem(ArrayList arrTOC, TOCItem rem)
		{
			for(int i=0; i<arrTOC.Count;i++)
			{
				TOCItem curItem = arrTOC[i] as TOCItem;

				if(curItem == rem)
				{
					arrTOC.RemoveAt(i);
					return true;
				}

				if(curItem.Children.Count > 0)
				{
					bool bRem = RemoveTOCItem(curItem.Children, rem);
					if(bRem)
						return true;
				}
			}

			return false;
		}

		/// <summary>
		/// Returns true if the MSHelpLibSystem instance contains 1 or more information types
		/// </summary>
		public bool HasInformationTypes
		{
			get { return (_informationTypes.Count>0); }
		}

		/// <summary>
		/// Returns true if the MSHelpLibSystem instance contains 1 or more categories
		/// </summary>
		public bool HasCategories
		{
			get { return (_categories.Count>0); }
		}

		/// <summary>
		/// Gets an ArrayList of <see cref="InformationType">InformationType</see> items
		/// </summary>
		public ArrayList InformationTypes
		{
			get { return _informationTypes; }
		}

		/// <summary>
		/// Gets an ArrayList of <see cref="Category">Category</see> items
		/// </summary>
		public ArrayList Categories
		{
			get { return _categories; }
		}

		/// <summary>
		/// Gets the information type specified by its name
		/// </summary>
		/// <param name="name">name of the information type to receive</param>
		/// <returns>Returns the Instance for the name or null if not found</returns>
		public InformationType GetInformationType(string name)
		{
			if(HasInformationTypes)
			{
				for(int i=0; i<_informationTypes.Count;i++)
				{
					InformationType iT = _informationTypes[i] as InformationType;

					if(iT.Name == name)
						return iT;
				}
			}

			return null;
		}

		/// <summary>
		/// Gets the category specifiyd by its name
		/// </summary>
		/// <param name="name">name of the category</param>
		/// <returns>Returns the Instance for the name or null if not found</returns>
		public Category GetCategory(string name)
		{
			if(HasCategories)
			{
				for(int i=0; i<_categories.Count;i++)
				{
					Category cat = _categories[i] as Category;

					if(cat.Name == name)
						return cat;
				}
			}

			return null;
		}

		#region Internal properties
		/// <summary>
		/// Gets the current storage wrapper.
		/// </summary>
		/// <remarks>This property will return not null, if there are currently file read actions running !</remarks>
		internal CHMStream CurrentStorageWrapper
		{
			get { return _currentWrapper;}
		}

		/// <summary>
		/// Gets the internal system file instance
		/// </summary>
		public CHMSystem SystemFile
		{
			get { return _systemFile; }
		}
		
		/// <summary>
		/// Gets the internal idxhdr file instance
		/// </summary>
		internal CHMIdxhdr IdxHdrFile
		{
			get { return _idxhdrFile; }
		}

		/// <summary>
		/// Gets the internal strings file instance
		/// </summary>
		public HelpStrings StringsFile
		{
			get { return _stringsFile; }
		}
		
		/// <summary>
		/// Gets the internal urlstr file instance
		/// </summary>
		public Urlstr UrlstrFile
		{
			get { return _urlstrFile; }
		}

		/// <summary>
		/// Gets the internal urltbl file instance
		/// </summary>
		public Urltable UrltblFile
		{
			get { return _urltblFile; }
		}

		/// <summary>
		/// Gets the internal topics file instance
		/// </summary>
		public Topics TopicsFile
		{
			get { return _topicsFile; }
		}

		/// <summary>
		/// Gets the internal tocidx file instance
		/// </summary>
		internal CHMTocidx TocidxFile
		{
			get { return _tocidxFile; }
		}

		/// <summary>
		/// Gets the internal btree file instance for alinks
		/// </summary>
		internal CHMBtree ALinksFile
		{
			get { return _aLinks; }
		}

		/// <summary>
		/// Gets the internal btree file instance for klinks
		/// </summary>
		internal CHMBtree KLinksFile
		{
			get { return _kLinks; }
		}		

		#endregion

		#region Properties from the #SYSTEM file
		/// <summary>
		/// Gets the file version of the chm file. 
		/// 2 for Compatibility=1.0,  3 for Compatibility=1.1
		/// </summary>
		internal int FileVersion
		{
			get 
			{ 
				if(_systemFile==null)
					return 0;

				return _systemFile.FileVersion; 
			}
		}

		/// <summary>
		/// Gets the contents file name
		/// </summary>
		internal string ContentsFile
		{
			get 
			{ 
				if(_systemFile==null)
					return String.Empty;

				return _systemFile.ContentsFile; 
			}
		}

		/// <summary>
		/// Gets the index file name
		/// </summary>
		internal string IndexFile
		{
			get 
			{ 
				if(_systemFile==null)
					return String.Empty;

				return _systemFile.IndexFile; 
			}
		}

		/// <summary>
		/// Gets the default help topic
		/// </summary>
		internal string DefaultTopic
		{
			get 
			{ 
				if(_systemFile==null)
					return String.Empty;

				return _systemFile.DefaultTopic; 
			}
		}

		/// <summary>
		/// Gets the title of the help window
		/// </summary>
		internal string HelpWindowTitle
		{
			get 
			{ 
				if(_systemFile==null)
					return String.Empty;

				return _systemFile.Title; 
			}
		}

		/// <summary>
		/// Gets the flag if DBCS is in use
		/// </summary>
		internal bool DBCS
		{
			get 
			{ 
				if(_systemFile==null)
					return false;

				return _systemFile.DBCS; 
			}
		}

		/// <summary>
		/// Gets the flag if full-text-search is available
		/// </summary>
		internal bool FullTextSearch
		{
			get 
			{ 
				if(_systemFile==null)
					return false;

				return _systemFile.FullTextSearch; 
			}
		}


        /// <summary>
        /// Gets a value indicating whether this instance can search.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can search; otherwise, <c>false</c>.
        /// </value>
		public override bool CanSearch
		{
			get 
			{ 
				return true;
			}
		}

		/// <summary>
		/// Gets the flag if the file has ALinks
		/// </summary>
		internal bool HasALinks
		{
			get 
			{ 
				if(_systemFile==null)
					return false;

				return _systemFile.HasALinks; 
			}
		}

		/// <summary>
		/// Gets the flag if the file has KLinks
		/// </summary>
		internal bool HasKLinks
		{
			get 
			{ 
				if(_systemFile==null)
					return false;

				return _systemFile.HasKLinks; 
			}
		}

		/// <summary>
		/// Gets the default window name
		/// </summary>
		internal string DefaultWindow
		{
			get 
			{ 
				if(_systemFile==null)
					return String.Empty;

				return _systemFile.DefaultWindow; 
			}
		}

		/// <summary>
		/// Gets the file name of the compile file
		/// </summary>
		internal string CompileFile
		{
			get 
			{ 
				if(_systemFile==null)
					return String.Empty;

				return _systemFile.CompileFile; 
			}
		}

		/// <summary>
		/// Gets the flag if the chm has a binary index file
		/// </summary>
		internal bool BinaryIndex
		{
			get 
			{ 
				if(_systemFile==null)
					return false;

				return _systemFile.BinaryIndex; 
			}
		}

		/// <summary>
		/// Gets the flag if the chm has a binary index file
		/// </summary>
		internal string CompilerVersion
		{
			get 
			{ 
				if(_systemFile==null)
					return String.Empty;

				return _systemFile.CompilerVersion; 
			}
		}

		/// <summary>
		/// Gets the flag if the chm has a binary toc file
		/// </summary>
		internal bool BinaryTOC
		{
			get 
			{ 
				if(_systemFile==null)
					return false;

				return _systemFile.BinaryTOC; 
			}
		}

		/// <summary>
		/// Gets the font face of the read font property.
		/// Empty string for default font.
		/// </summary>
		internal string FontFace
		{
			get
			{
				if(_systemFile==null)
					return "";
					
				return _systemFile.FontFace; 
			}
		}

		/// <summary>
		/// Gets the font size of the read font property.
		/// 0 for default font size
		/// </summary>
		internal double FontSize
		{
			get
			{
				if(_systemFile==null)
					return 0;
					
				return _systemFile.FontSize; 
			}
		}

		/// <summary>
		/// Gets the character set of the read font property
		/// 1 for default
		/// </summary>
		internal int CharacterSet
		{
			get
			{
				if(_systemFile==null)
					return 1;
					
				return _systemFile.CharacterSet; 
			}
		}

		/// <summary>
		/// Gets the codepage depending on the read font property
		/// </summary>
		internal int CodePage
		{
			get
			{
				if(_systemFile==null)
					return CultureInfo.CurrentCulture.TextInfo.ANSICodePage;
					
				return _systemFile.CodePage; 
			}
		}

		/// <summary>
		/// Gets the assiciated culture info
		/// </summary>
		internal CultureInfo Culture
		{
			get 
			{ 
				if(_systemFile==null)
					return CultureInfo.CurrentCulture;
					
				return _systemFile.Culture; 
			}
		}

		#endregion 

		#region Properties from the #IDXHDR file
		/// <summary>
		/// Gets the number of topic nodes including the contents and index files
		/// </summary>
		internal int NumberOfTopicNodes
		{
			get 
			{
				if(_idxhdrFile==null)
					return 0;

				return _idxhdrFile.NumberOfTopicNodes; 
			}
		}

		/// <summary>
		/// Gets the ImageList string specyfied in the #IDXHDR file.
		/// </summary>
		/// <remarks>This property uses the #STRINGS file to extract the string at a given offset.</remarks>
		internal string ImageList
		{
			get 
			{
				if( (_stringsFile == null) || (_idxhdrFile == null) )
					return "";

				return _stringsFile[ _idxhdrFile.ImageListOffset ];
			}
		}

		/// <summary>
		/// Gets the background setting 
		/// </summary>
		internal int Background
		{
			get 
			{ 
				if(_idxhdrFile==null)
					return 0;

				return _idxhdrFile.Background; 
			}
		}

		/// <summary>
		/// Gets the foreground setting 
		/// </summary>
		internal int Foreground
		{
			get 
			{ 
				if(_idxhdrFile==null)
					return 0;

				return _idxhdrFile.Foreground; 
			}
		}

		/// <summary>
		/// Gets the Font string specyfied in the #IDXHDR file.
		/// </summary>
		/// <remarks>This property uses the #STRINGS file to extract the string at a given offset.</remarks>
		internal string FontName
		{
			get 
			{
				if( (_stringsFile == null) || (_idxhdrFile == null) )
					return "";

				return _stringsFile[ _idxhdrFile.FontOffset ];
			}
		}

		/// <summary>
		/// Gets the FrameName string specyfied in the #IDXHDR file.
		/// </summary>
		/// <remarks>This property uses the #STRINGS file to extract the string at a given offset.</remarks>
		internal string FrameName
		{
			get 
			{
				if( (_stringsFile == null) || (_idxhdrFile == null) )
					return "";

				return _stringsFile[ _idxhdrFile.FrameNameOffset ];
			}
		}

		/// <summary>
		/// Gets the WindowName string specyfied in the #IDXHDR file.
		/// </summary>
		/// <remarks>This property uses the #STRINGS file to extract the string at a given offset.</remarks>
		internal string WindowName
		{
			get 
			{
				if( (_stringsFile == null) || (_idxhdrFile == null) )
					return "";

				return _stringsFile[ _idxhdrFile.WindowNameOffset ];
			}
		}

		/// <summary>
		/// Gets a string array containing the merged file names
		/// </summary>
		internal string[] MergedFiles
		{
			get
			{
				if( (_stringsFile == null) || (_idxhdrFile == null) )
					return new string[0];

				string[] saRet = new string[ _idxhdrFile.MergedFileOffsets.Count ];

				for(int i=0; i < _idxhdrFile.MergedFileOffsets.Count; i++)
				{
					saRet[i] = _stringsFile[ (int)_idxhdrFile.MergedFileOffsets[i] ];
				}

				return saRet;
			}
		}
		#endregion

		/// <summary>
		/// Gets the file info associated with this instance
		/// </summary>
		public ChmFileInfo FileInfo
		{
			get { return _chmFileInfo; }
		}

		/// <summary>
		/// Gets the full-text search engine for this file
		/// </summary>
		internal FullTextEngine FullTextSearchEngine
		{
			get { return _ftSearcher; }
		}

		/// <summary>
		/// Gets the full pathname of the file
		/// </summary>
		public string ChmFilePath
		{
			get { return _chmFileName; }
		}

		/// <summary>
		/// Gets the full pathname of the chi-file
		/// The file name is zero-length if there is no chi-file
		/// </summary>
		public string ChiFilePath
		{
			get { return _chiFileName; }
		}

		/// <summary>
		/// Gets the full pathname of the chw-file
		/// The file name is zero-length if there is no chw-file
		/// </summary>
		public string ChwFilePath
		{
			get { return _chwFileName; }
		}

		/// <summary>
		/// Gets the full pathname of the chq-file
		/// The file name is zero-length if there is no chq-file
		/// </summary>
		public string ChqFilePath
		{
			get { return _chqFileName; }
		}

		/// <summary>
		/// Forms an URL for the web browser
		/// </summary>
		/// <param name="local">local resource</param>
		/// <returns>a url for the web-browser</returns>
		internal string FormURL(string local)
		{
			if( (local.ToLower().IndexOf("http://") >= 0) ||
				(local.ToLower().IndexOf("https://") >= 0) ||
				(local.ToLower().IndexOf("mailto:") >= 0) ||
				(local.ToLower().IndexOf("ftp://") >= 0)  ||
				(local.ToLower().IndexOf("ms-its:") >= 0))				
				return local;

			return UrlPrefix + _chmFileName + "::/" + local;
		}
		
		/// <summary>
		/// Implement IDisposable.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			// This object will be cleaned up by the Dispose method.
			// Therefore, you should call GC.SupressFinalize to
			// take this object off the finalization queue 
			// and prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}


		/// <summary>
		/// Dispose(bool disposing) executes in two distinct scenarios. 
		/// If disposing equals true, the method has been called directly 
		/// or indirectly by a user's code. Managed and unmanaged resources 
		/// can be disposed. 
		/// If disposing equals false, the method has been called by the 
		/// runtime from inside the finalizer and you should not reference  
		/// other objects. Only unmanaged resources can be disposed.
		/// </summary>
		/// <param name="disposing">disposing flag</param>
		private void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called.
			if(!this.disposed)
			{
				// If disposing equals true, dispose all managed 
				// and unmanaged resources.
				if(disposing)
				{
					// Dispose managed resources.
					_toc.Clear();
					_indexKLinks.Clear();
					_indexALinks.Clear();

					if(_systemFile!=null)
						_systemFile.Dispose();

					if(_idxhdrFile != null)
						_idxhdrFile.Dispose();

					if(_stringsFile != null)
						_stringsFile.Dispose();

					if(_urlstrFile != null)
						_urlstrFile.Dispose();

					if(_urltblFile != null)
						_urltblFile.Dispose();

					if(_topicsFile != null)
						_topicsFile.Dispose();

					if(_tocidxFile != null)
						_tocidxFile.Dispose();

					if(_kLinks != null)
						_kLinks.Dispose();

					if(_aLinks != null)
						_aLinks.Dispose();

					if(_ftSearcher != null)
						_ftSearcher.Dispose();

					if(_chmFileInfo != null)
						_chmFileInfo = null;
				}
			}
			disposed = true;         
		}

		#region IHelpFile Members

        /// <summary>
        /// Opens the specified system instance.
        /// </summary>
        /// <param name="systemInstance">The system instance.</param>
        /// <param name="chmFile">The CHM file.</param>
        /// <param name="_flags">The _flags.</param>
        /// <param name="SH">The SH.</param>
        /// <param name="PH">The PH.</param>
        /// <returns></returns>
		public override bool Open(IHelpSystem systemInstance, string chmFile, int  _flags, StatusHandler SH, ProgressHandler PH)
		{
			_urlPrefix = "ms-its:";
			bool rc=base.Open(systemInstance, chmFile, _flags, SH, PH);
			if (rc==false)
				return false;

			_FileName=chmFile;			
			CHMName=m_FInfo.Name;
			
			OnStatus("Loading "+CHMName);
			if( ! chmFile.ToLower().EndsWith(".chm") )
			{
				throw new ArgumentException("MSHelpLib file must have the extension .chm !", "chmFile");
			}

			_chmFileName = chmFile;
			_chiFileName = "";

			// Read the IStorage file system
			if( File.Exists(chmFile) )
			{	
				if ((Flags & (int)LoadFlags.LoadDumpFile)!=0)
				{
					OnStatus("Checking for QuickLoad Data");
					if (this.LoadDump(this))
					{
						return true;
					}				
				}

				string sCHIName = _chmFileName.Substring(0, _chmFileName.Length-3) + "chi";
				string sCHQName = _chmFileName.Substring(0, _chmFileName.Length-3) + "chq";
				string sCHWName = _chmFileName.Substring(0, _chmFileName.Length-3) + "chw";

				// If there is a CHI file present (this file includes the internal system files for the current chm file)
				if( File.Exists(sCHIName) )
				{
					_chiFileName = sCHIName;
				
					ReadFile(_chiFileName, MSHelpLibFileType.CHI);
				}

				// If there is a CHW file present (this file includes the internal binary index of the help)
				if(( File.Exists(sCHWName) ) && (!m_onlySystemData) )
				{
					_chwFileName = sCHWName;
				
					ReadFile(_chwFileName, MSHelpLibFileType.CHW);
				}

				// If there is a CHQ file present (this file includes the fulltext-search data)
				if(( File.Exists(sCHQName) ) && (!m_onlySystemData) )
				{
					_chqFileName = sCHQName;
				
					ReadFile(_chqFileName, MSHelpLibFileType.CHQ);
				}
				
				_chmFileInfo = new ChmFileInfo(this);
				ReadFile(chmFile, MSHelpLibFileType.CHM);	

				if ((Flags & (int)(LoadFlags.CreateSearchIndexOnLoad))!=0)
					InitSearch();

				// Compact Index
				if ((this.Index!=null) && (this.Index.KLinks!=null))
				{
					this.OnStatus("Compacting Index");
					MSHelper.CompactIndex(Index.KLinks);			
				}

				if ((Flags & (int)(LoadFlags.SaveDumpFile))!=0)
				{
					OnStatus("Writing QuickLoad Data");
					SaveDump();
				}				
			}
			return true;
		}

		#endregion
	}
}
